/*
 * FreeRTOS Kernel <DEVELOPMENT BRANCH>
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/* FreeRTOS includes. */
#include "FreeRTOSConfig.h"

/* Xilinx library includes. */
#include "microblaze_exceptions_g.h"
#include "xparameters.h"

/* Offsets from the stack pointer at which saved registers are placed. */
#define portR31_OFFSET  4
#define portR30_OFFSET  8
#define portR29_OFFSET  12
#define portR28_OFFSET  16
#define portR27_OFFSET  20
#define portR26_OFFSET  24
#define portR25_OFFSET  28
#define portR24_OFFSET  32
#define portR23_OFFSET  36
#define portR22_OFFSET  40
#define portR21_OFFSET  44
#define portR20_OFFSET  48
#define portR19_OFFSET  52
#define portR18_OFFSET  56
#define portR17_OFFSET  60
#define portR16_OFFSET  64
#define portR15_OFFSET  68
#define portR14_OFFSET  72
#define portR13_OFFSET  76
#define portR12_OFFSET  80
#define portR11_OFFSET  84
#define portR10_OFFSET  88
#define portR9_OFFSET   92
#define portR8_OFFSET   96
#define portR7_OFFSET   100
#define portR6_OFFSET   104
#define portR5_OFFSET   108
#define portR4_OFFSET   112
#define portR3_OFFSET   116
#define portR2_OFFSET   120
#define portCRITICAL_NESTING_OFFSET 124
#define portMSR_OFFSET 128

#if( XPAR_MICROBLAZE_USE_FPU != 0 )
    #define portFSR_OFFSET 132
    #if( XPAR_MICROBLAZE_USE_STACK_PROTECTION )
        #define portSLR_OFFSET 136
        #define portSHR_OFFSET 140

        #define portCONTEXT_SIZE 144
        #define portMINUS_CONTEXT_SIZE -144
    #else
        #define portCONTEXT_SIZE 136
        #define portMINUS_CONTEXT_SIZE -136
    #endif
#else
    #if( XPAR_MICROBLAZE_USE_STACK_PROTECTION )
        #define portSLR_OFFSET 132
        #define portSHR_OFFSET 136

        #define portCONTEXT_SIZE 140
        #define portMINUS_CONTEXT_SIZE -140
    #else
        #define portCONTEXT_SIZE 132
        #define portMINUS_CONTEXT_SIZE -132
    #endif
#endif

    .extern pxCurrentTCB
    .extern XIntc_DeviceInterruptHandler
    .extern vTaskSwitchContext
    .extern uxCriticalNesting
    .extern pulISRStack
    .extern ulTaskSwitchRequested
    .extern vPortExceptionHandler
    .extern pulStackPointerOnFunctionEntry

    .global _interrupt_handler
    .global VPortYieldASM
    .global vPortStartFirstTask
    .global vPortExceptionHandlerEntry


.macro portSAVE_CONTEXT

    /* Make room for the context on the stack. */
    addik r1, r1, portMINUS_CONTEXT_SIZE

    /* Stack general registers. */
    swi r31, r1, portR31_OFFSET
    swi r30, r1, portR30_OFFSET
    swi r29, r1, portR29_OFFSET
    swi r28, r1, portR28_OFFSET
    swi r27, r1, portR27_OFFSET
    swi r26, r1, portR26_OFFSET
    swi r25, r1, portR25_OFFSET
    swi r24, r1, portR24_OFFSET
    swi r23, r1, portR23_OFFSET
    swi r22, r1, portR22_OFFSET
    swi r21, r1, portR21_OFFSET
    swi r20, r1, portR20_OFFSET
    swi r19, r1, portR19_OFFSET
    swi r18, r1, portR18_OFFSET
    swi r17, r1, portR17_OFFSET
    swi r16, r1, portR16_OFFSET
    swi r15, r1, portR15_OFFSET
    /* R14 is saved later as it needs adjustment if a yield is performed. */
    swi r13, r1, portR13_OFFSET
    swi r12, r1, portR12_OFFSET
    swi r11, r1, portR11_OFFSET
    swi r10, r1, portR10_OFFSET
    swi r9, r1, portR9_OFFSET
    swi r8, r1, portR8_OFFSET
    swi r7, r1, portR7_OFFSET
    swi r6, r1, portR6_OFFSET
    swi r5, r1, portR5_OFFSET
    swi r4, r1, portR4_OFFSET
    swi r3, r1, portR3_OFFSET
    swi r2, r1, portR2_OFFSET

    /* Stack the critical section nesting value. */
    lwi r18, r0, uxCriticalNesting
    swi r18, r1, portCRITICAL_NESTING_OFFSET

    /* Stack MSR. */
    mfs r18, rmsr
    swi r18, r1, portMSR_OFFSET

    #if( XPAR_MICROBLAZE_USE_FPU != 0 )
        /* Stack FSR. */
        mfs r18, rfsr
        swi r18, r1, portFSR_OFFSET
    #endif

#if( XPAR_MICROBLAZE_USE_STACK_PROTECTION )
    /* Save the stack limits */
    mfs r18, rslr
    swi r18, r1, portSLR_OFFSET
    mfs r18, rshr
    swi r18, r1, portSHR_OFFSET
#endif

    /* Save the top of stack value to the TCB. */
    lwi r3, r0, pxCurrentTCB
    sw  r1, r0, r3

    .endm

.macro portRESTORE_CONTEXT

    /* Load the top of stack value from the TCB. */
    lwi r18, r0, pxCurrentTCB
    lw  r1, r0, r18

#if( XPAR_MICROBLAZE_USE_STACK_PROTECTION )
    /* Restore the stack limits -- must not load from r1 (Stack Pointer)
    because if the address of load or store instruction is out of range,
    it will trigger Stack Protection Violation exception. */
    or  r18, r0, r1
    lwi r12, r18, portSLR_OFFSET
    mts rslr, r12
    lwi r12, r18, portSHR_OFFSET
    mts rshr, r12
#endif

    /* Restore the general registers. */
    lwi r31, r1, portR31_OFFSET
    lwi r30, r1, portR30_OFFSET
    lwi r29, r1, portR29_OFFSET
    lwi r28, r1, portR28_OFFSET
    lwi r27, r1, portR27_OFFSET
    lwi r26, r1, portR26_OFFSET
    lwi r25, r1, portR25_OFFSET
    lwi r24, r1, portR24_OFFSET
    lwi r23, r1, portR23_OFFSET
    lwi r22, r1, portR22_OFFSET
    lwi r21, r1, portR21_OFFSET
    lwi r20, r1, portR20_OFFSET
    lwi r19, r1, portR19_OFFSET
    lwi r17, r1, portR17_OFFSET
    lwi r16, r1, portR16_OFFSET
    lwi r15, r1, portR15_OFFSET
    lwi r14, r1, portR14_OFFSET
    lwi r13, r1, portR13_OFFSET
    lwi r12, r1, portR12_OFFSET
    lwi r11, r1, portR11_OFFSET
    lwi r10, r1, portR10_OFFSET
    lwi r9, r1, portR9_OFFSET
    lwi r8, r1, portR8_OFFSET
    lwi r7, r1, portR7_OFFSET
    lwi r6, r1, portR6_OFFSET
    lwi r5, r1, portR5_OFFSET
    lwi r4, r1, portR4_OFFSET
    lwi r3, r1, portR3_OFFSET
    lwi r2, r1, portR2_OFFSET

    /* Reload the rmsr from the stack. */
    lwi r18, r1, portMSR_OFFSET
    mts rmsr, r18

    #if( XPAR_MICROBLAZE_USE_FPU != 0 )
        /* Reload the FSR from the stack. */
        lwi r18, r1, portFSR_OFFSET
        mts rfsr, r18
    #endif

    /* Load the critical nesting value. */
    lwi r18, r1, portCRITICAL_NESTING_OFFSET
    swi r18, r0, uxCriticalNesting

    /* Test the critical nesting value.  If it is non zero then the task last
    exited the running state using a yield.  If it is zero, then the task
    last exited the running state through an interrupt. */
    xori r18, r18, 0
    bnei r18, exit_from_yield

    /* r18 was being used as a temporary.  Now restore its true value from the
    stack. */
    lwi r18, r1, portR18_OFFSET

    /* Remove the stack frame. */
    addik r1, r1, portCONTEXT_SIZE

    /* Return using rtid so interrupts are re-enabled as this function is
    exited. */
    rtid r14, 0
    or r0, r0, r0

    .endm

/* This function is used to exit portRESTORE_CONTEXT() if the task being
returned to last left the Running state by calling taskYIELD() (rather than
being preempted by an interrupt). */
    .text
    .align  4
exit_from_yield:

    /* r18 was being used as a temporary.  Now restore its true value from the
    stack. */
    lwi r18, r1, portR18_OFFSET

    /* Remove the stack frame. */
    addik r1, r1, portCONTEXT_SIZE

    /* Return to the task. */
    rtsd r14, 0
    or r0, r0, r0


    .text
    .align  4
_interrupt_handler:

    portSAVE_CONTEXT

    /* Stack the return address. */
    swi r14, r1, portR14_OFFSET

    /* Switch to the ISR stack. */
    lwi r1, r0, pulISRStack

#if( XPAR_MICROBLAZE_USE_STACK_PROTECTION )
    ori r18, r0, _stack_end
    mts rslr, r18
    ori r18, r0, _stack
    mts rshr, r18
#endif

    /* The parameter to the interrupt handler. */
    ori r5, r0, configINTERRUPT_CONTROLLER_TO_USE

    /* Execute any pending interrupts. */
    bralid r15, XIntc_DeviceInterruptHandler
    or r0, r0, r0

    /* See if a new task should be selected to execute. */
    lwi r18, r0, ulTaskSwitchRequested
    or r18, r18, r0

    /* If ulTaskSwitchRequested is already zero, then jump straight to
    restoring the task that is already in the Running state. */
    beqi r18, task_switch_not_requested

    /* Set ulTaskSwitchRequested back to zero as a task switch is about to be
    performed. */
    swi r0, r0, ulTaskSwitchRequested

    /* ulTaskSwitchRequested was not 0 when tested.  Select the next task to
    execute. */
    bralid r15, vTaskSwitchContext
    or r0, r0, r0

task_switch_not_requested:

    /* Restore the context of the next task scheduled to execute. */
    portRESTORE_CONTEXT


    .text
    .align  4
VPortYieldASM:

    portSAVE_CONTEXT

    /* Modify the return address so a return is done to the instruction after
    the call to VPortYieldASM. */
    addi r14, r14, 8
    swi r14, r1, portR14_OFFSET

    /* Switch to use the ISR stack. */
    lwi r1, r0, pulISRStack

#if( XPAR_MICROBLAZE_USE_STACK_PROTECTION )
    ori r18, r0, _stack_end
    mts rslr, r18
    ori r18, r0, _stack
    mts rshr, r18
#endif

    /* Select the next task to execute. */
    bralid r15, vTaskSwitchContext
    or r0, r0, r0

    /* Restore the context of the next task scheduled to execute. */
    portRESTORE_CONTEXT

    .text
    .align  4
vPortStartFirstTask:

    portRESTORE_CONTEXT



#if ( MICROBLAZE_EXCEPTIONS_ENABLED == 1 ) && ( configINSTALL_EXCEPTION_HANDLERS == 1 )

    .text
    .align 4
vPortExceptionHandlerEntry:

    /* Take a copy of the stack pointer before vPortExecptionHandler is called,
    storing its value prior to the function stack frame being created. */
    swi r1, r0, pulStackPointerOnFunctionEntry
    bralid r15, vPortExceptionHandler
    or r0, r0, r0

#endif /* ( MICROBLAZE_EXCEPTIONS_ENABLED == 1 ) && ( configINSTALL_EXCEPTION_HANDLERS == 1 ) */
